#ifndef ROOM_HPP_INCLUDED
#define ROOM_HPP_INCLUDED
#include "locker.h"
#include "request.hpp"
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <math.h>
#include <fstream>
#include <assert.h>
#include <list>
#include <json/json.h>
#include <netinet/in.h>
#include "database.hpp"
#include <QMessageBox>
#include <time.h>
#include "statistics.hpp"

extern database db;

using namespace std;

constexpr int timeslot=60;
constexpr double tempDiff=1;

static int cc=0;



struct roomInfo
{
    roomInfo(const string& _roomNo,bool _isOpen,int _wind,double _temperature,double _currentTemperature)
        :roomNo(_roomNo),
         isOpen(_isOpen),
         wind(_wind),
         s_wind(_wind),
         temperature(_temperature),
         currentTemperature(_currentTemperature),
         isServed(false),
         cost(0),
         energy(0),
         timeLeft(timeslot),
         runtime(0),
         sessionId(0),
         switchTime(0),
         achieveTempTime(0),
         scheduleTime(0){
        for(int i=16;i<=30;++i){
            tempSta.push_back(make_pair(i,0));
        }
        windSta.push_back(make_pair(1,0));
        windSta.push_back(make_pair(1,0));
        windSta.push_back(make_pair(3,0));
    }
    string roomNo;

    bool isOpen;
    bool isServed;
    int s_wind;
    int wind;
    double temperature;
    double currentTemperature;
    double cost;
    double energy;
    int timeLeft;
    int runtime;
    time_t sessionId;//If sessionId==0, means that this room is free.

    int switchTime;
    int achieveTempTime;
    int scheduleTime;
    vector<pair<int,int> > tempSta;
    vector<pair<int,int> > windSta;
};

class rooms
{
private:
    list<roomInfo*> roomsBeServered;
    list<roomInfo*> roomsInQueue;
    locker roomLock;
    unsigned maxRoomInService;
    static double price;
    bool mode;
public:
    unordered_map<string,roomInfo> room;
    rooms(unsigned int _num):maxRoomInService(_num) {
        ifstream fin;
        fin.open("/tmp/mode");
        string m;
        fin>>m;
        fin.close();
        if(m==string("h")){
            mode=true;
        }
        else{
            mode=false;
        }
    }

    void setSessionId(const string& roomNo){
        roomLock.lock();
        auto ret=room.find(roomNo);
        if(ret!=room.end()){
            if(ret->second.sessionId==0)
                ret->second.sessionId=time(nullptr);
        }
        roomLock.unlock();
    }

    void clearState(const string& roomNo){
        roomLock.lock();
        auto ret=room.find(roomNo);
        if(ret!=room.end()){
            ret->second.isOpen=false;
            ret->second.cost=0;
            ret->second.wind=0;
            ret->second.temperature=25;
            ret->second.sessionId=0;
        }
        roomLock.unlock();
    }

    time_t getSessionId(const string& roomNo){
        roomLock.lock();
        auto ret=room.find(roomNo);
        if(ret!=room.end()){
            auto result=ret->second.sessionId;
            roomLock.unlock();
            return result;
        }
        roomLock.unlock();
        return 0;
    }

    double getCost(const string& roomNo){
        roomLock.lock();
        auto ret=room.find(roomNo);
        if(ret!=room.end()){
            auto result=ret->second.cost;
            roomLock.unlock();
            return result;
        }
        roomLock.unlock();
        return 0;
    }

    bool isPermitted(const string& roomNo){
        roomLock.lock();
        auto ret=room.find(roomNo);
        if(ret!=room.end()){
            auto result=ret->second.sessionId;
            roomLock.unlock();
            return result!=0?true:false;
        }
        roomLock.unlock();
        return true;
    }

    bool isExist(const string& roomNo)
    {
        roomLock.lock();
        if(room.find(roomNo)==room.end())
        {
            roomLock.unlock();
            return false;
        }
        roomLock.unlock();
        return true;
    }
    bool insert(const string& roomNo,const roomInfo& rInfo)
    {

        roomLock.lock();
        auto ret=room.insert(make_pair(roomNo,rInfo));
        if(roomsBeServered.size()<maxRoomInService)
        {
            cout<<roomNo<<" has been inserted into servedQueue;"<<endl;
            roomsBeServered.push_back(&(ret.first->second));
            ret.first->second.isServed=true;
        }
        else
        {
            cout<<roomNo<<" has been inserted into blockQueue;"<<endl;
            roomsInQueue.push_back(&(ret.first->second));
            ret.first->second.isServed=false;
            ret.first->second.wind=-1;
        }
        roomLock.unlock();
        return true;

    }
    bool modify(const string& _roomNo,double _temperature,int _wind,bool _isOpen)
    {
        cout<<"enter modify"<<endl;
        auto iter=room.find(_roomNo);
        roomLock.lock();
        if(iter!=room.end())
        {
            auto instance=statistics::getInstance();
            if(std::find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second))!=roomsInQueue.end()){
                iter->second.s_wind=_wind;
            }
            else{
                iter->second.wind=_wind;
                iter->second.s_wind=_wind;
            }
            if(_isOpen!=iter->second.isOpen){
                (instance->switchTime)++;
                (iter->second.switchTime)++;
            }
            if(_isOpen==true&&iter->second.isOpen==false){
                if(roomsBeServered.size()<maxRoomInService){
                    iter->second.isServed=true;
                    iter->second.wind=iter->second.s_wind;
                    roomsBeServered.push_back(&(iter->second));
                }
                else{
                    iter->second.isServed=false;
                    iter->second.wind=0;
                    roomsInQueue.push_back(&(iter->second));
                }
            }

            iter->second.isOpen=_isOpen;
            if(_isOpen==false){
                if(iter->second.isServed==true){
                    auto itor=find(roomsBeServered.begin(),roomsBeServered.end(),&(iter->second));
                    roomsBeServered.erase(itor);
                    iter->second.wind=0;
                    iter->second.isServed=false;
                    if(!roomsInQueue.empty()){
                        auto minItor=std::min_element(roomsInQueue.begin(),roomsInQueue.end(),
                                                      [](roomInfo *i,roomInfo *j)->bool{return i->timeLeft<j->timeLeft;});
                        if(minItor!=roomsInQueue.end()){
                            (*minItor)->wind=(*minItor)->s_wind;
                            (*minItor)->isServed=true;
                            ((*minItor)->scheduleTime)++;
                        }
                        roomsBeServered.push_back((*minItor));
                        roomsInQueue.erase(minItor);
                    }
                }
                else{
                    auto itor=find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second));
                    roomsInQueue.erase(itor);
                }

            }
            if(this->mode==true){
                if(_temperature<26){
                    roomLock.unlock();
                    return false;
                }
            }
            else{
                if(_temperature>25){
                    roomLock.unlock();
                    return false;
                }
            }
            iter->second.temperature=_temperature;

            QSqlQuery query(database::getInstance()->getDB());
            auto ret=query.exec(QString("INSERT INTO records VALUES(%1,%2,'%3',%4,%5,%6,%7,'%8',%9)")
                       .arg(QString().setNum(iter->second.sessionId))
                       .arg(QString().setNum(time(nullptr)))
                       .arg(QString::fromStdString(iter->second.roomNo))
                       .arg(QString().setNum(static_cast<int>(iter->second.temperature)))
                       .arg(QString().setNum(iter->second.wind))
                       .arg(QString().setNum(iter->second.isOpen?1:0))
                       .arg(QString().setNum(iter->second.isServed?1:0))
                       .arg(QString("modify"))
                       .arg(QString().setNum(iter->second.cost)));
            //assert(ret);
            if(_wind>2&&(((mode==false)&&(fabs(iter->second.currentTemperature-_temperature)<0.01||iter->second.currentTemperature>_temperature))||
                         ((mode==true)&&(fabs(iter->second.currentTemperature-_temperature<0.01||iter->second.currentTemperature<_temperature)))
                    )&&iter->second.isServed==false){
                if(roomsBeServered.size()>=maxRoomInService){
                    auto maxItor=std::max_element(roomsBeServered.begin(),roomsBeServered.end(),
                                                  [](roomInfo *i,roomInfo *j)->bool{return i->runtime<j->runtime;});
                    auto tmp=std::find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second));
                    (*maxItor)->isServed=false;
                    (*maxItor)->wind=0;
                    (*maxItor)->timeLeft=timeslot;
                    (*tmp)->isServed=true;
                    (*tmp)->wind=(*tmp)->s_wind;
                    ((*tmp)->scheduleTime)++;
                    roomsInQueue.push_back((*maxItor));
                    roomsBeServered.push_back((*tmp));
                    roomsInQueue.erase(tmp);
                    roomsBeServered.erase(maxItor);
                    cout<<"BBB"<<endl;
                }
                else{
                    auto tmp=std::find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second));
                    (*tmp)->isServed=true;
                    (*tmp)->wind=(*tmp)->s_wind;
                    ((*tmp)->scheduleTime)++;
                    roomsBeServered.push_back((*tmp));
                    roomsInQueue.erase(tmp);
                    cout<<"CCC"<<endl;
                }
            }
            /*else if((((mode==false)&&(iter->second.currentTemperature<_temperature))||
                     ((mode==true)&&(iter->second.currentTemperature>_temperature)))
                ){
                if(roomsBeServered.size()>=maxRoomInService){
                    auto maxItor=std::max_element(roomsBeServered.begin(),roomsBeServered.end(),
                                                  [](roomInfo *i,roomInfo *j)->bool{return i->runtime<j->runtime;});
                    auto tmp=std::find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second));
                    (*maxItor)->isServed=false;
                    (*maxItor)->wind=-1;
                    (*maxItor)->timeLeft=timeslot;
                    (*tmp)->isServed=true;
                    (*tmp)->wind=(*tmp)->s_wind;
                    ((*tmp)->scheduleTime)++;
                    roomsInQueue.push_back((*maxItor));
                    roomsBeServered.push_back((*tmp));
                    roomsInQueue.erase(tmp);
                    roomsBeServered.erase(maxItor);
                    cout<<"BBB"<<endl;
                }
                else{
                    auto tmp=std::find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second));
                    (*tmp)->isServed=true;
                    (*tmp)->wind=(*tmp)->s_wind;
                    ((*tmp)->scheduleTime)++;
                    roomsBeServered.push_back((*tmp));
                    roomsInQueue.erase(tmp);
                    cout<<"CCC"<<endl;
                }
            }*/
        }
        roomLock.unlock();
        return true;
    }
    bool notify(const string& _roomNo,double _curTemp)
    {
        auto iter=room.find(_roomNo);
        roomLock.lock();
        if(iter!=room.end())
        {
            iter->second.currentTemperature=_curTemp;
        }
        roomLock.unlock();
        return true;
    }
    bool remove(const string& _roomNo)
    {
        auto iter=room.find(_roomNo);
        roomLock.lock();
        auto ite_1=find_if(roomsBeServered.begin(),roomsBeServered.end(),[_roomNo](roomInfo* i)->bool {return i->roomNo==_roomNo;});
        if(ite_1==roomsBeServered.end())
        {
            auto ite_2=find_if(roomsInQueue.begin(),roomsInQueue.end(),[_roomNo](roomInfo *i)->bool {return i->roomNo==_roomNo;});
            if(ite_2!=roomsInQueue.end())
            {
                roomsInQueue.erase(ite_2);
            }
        }
        else
        {
            roomsBeServered.erase(ite_1);
        }
        room.erase(iter);
        roomLock.unlock();
        return true;
    }
    bool charge(const string& _roomNo)
    {
        cout<<"enter charge"<<endl;
        roomLock.lock();
        auto iter=room.find(_roomNo);
        if(iter->second.wind==0){
            roomLock.unlock();
            return false;
        }
        cout<<"charge lock"<<endl;
        iter->second.runtime+=1;
        iter->second.energy+=iter->second.wind;
        iter->second.cost+=(static_cast<double>(iter->second.wind)*1.2/60);
        roomLock.unlock();
        cout<<"charge unlock"<<endl;
        return true;
    }
    bool feedBack(const string& _roomNo,int _fd)
    {
        cout<<"["<<cc++<<"]"<<endl;
        cout<<"served"<<endl;
        for(auto &i:roomsBeServered){
            cout<<i->roomNo<<endl;
        }
        cout<<"block"<<endl;
        for(auto &i:roomsInQueue){
            cout<<i->roomNo<<endl;
        }
        auto iter=room.find(_roomNo);
        Json::Value root;
        Json::FastWriter writer;
        Json::Value info;
        info["switch"] = iter->second.isOpen?1:0;
        info["temperature"] = iter->second.temperature;
        /*if(iter->second.isServed){
            info["wind"]=iter->second.wind;
        }
        else{
            info["wind"]=iter->second.wind;
        }*/
        //info["wind"]=(iter->second.isServed)?(iter->second.wind):0;
        info["wind"]=iter->second.wind;
        info["cost"]=iter->second.cost;
        //info["isServed"]=iter->second.isServed?1:0;
        root.append(info);
        string j=writer.write(root);
        //cout<<(iter->second.isOpen?1:0)<<" "<<iter->second.temperature<<" "<<iter->second.wind<<" "<<iter->second.cost<<" "<<iter->second.currentTemperature<<endl;
        cout<<j<<endl;
        j.pop_back();
        j.pop_back();
        j.erase(j.begin());
        int size=j.size();
        //send(_fd,&size,sizeof(int),0);
        send(_fd,j.c_str(),j.size(),0);
        return true;
    }
    void scheduleRR(const string& roomNo)
    {
        cout<<"enter RR"<<endl;
        //roomLock.lock();
        if(roomsBeServered.size()<maxRoomInService){
            auto swapInItr=find_if(roomsInQueue.begin(),roomsInQueue.end(),[](roomInfo* i)->bool{return i->isOpen==true;});
            if(swapInItr!=roomsInQueue.end()){
                (*swapInItr)->isServed=true;
                (*swapInItr)->timeLeft=timeslot;
                roomsBeServered.push_back(*swapInItr);
                roomsInQueue.erase(swapInItr);
            }
        }
        auto itor=room.find(roomNo);
        if(itor!=room.end()){
            if(itor->second.isServed==true){
                if(itor->second.isOpen==false){
                    swapInAndOut(roomNo);
                    return;
                }
                --(itor->second.timeLeft);
                if(itor->second.timeLeft<=0)
                {
                    swapInAndOut(roomNo);
                }
            }
        }
        //roomLock.unlock();
    }
    void schedulePriority()
    {

    }

    void swapInAndOut(const string& roomNo){
        cout<<"enter sio"<<endl;
        auto swapOutItr=find_if(roomsBeServered.begin(),roomsBeServered.end(),[roomNo](roomInfo* i)->bool{return i->roomNo==roomNo;});
        auto swapOutPtr=*swapOutItr;
        swapOutPtr->isServed=false;
        roomsBeServered.erase(swapOutItr);
        roomsInQueue.push_back(swapOutPtr);
        auto swapInItr=find_if(roomsInQueue.begin(),roomsInQueue.end(),[](roomInfo* i)->bool{return i->isOpen==true;});
        if(swapInItr==roomsInQueue.end()){
            return;
        }
        else{
            (*swapInItr)->isServed=true;
            (*swapInItr)->timeLeft=timeslot;
            roomsBeServered.push_back(*swapInItr);
            roomsInQueue.erase(swapInItr);
        }
    }

    void calculate(const string& _roomNo,double temperature){
        roomLock.lock();
        auto iter=room.find(_roomNo);
        //iter->second.currentTemperature=temperature;
        if(iter->second.isOpen==false){
            roomLock.unlock();
            return;
        }
        auto instance = statistics::getInstance();
        instance->tempSta[iter->second.temperature-16].second+=1;
        iter->second.tempSta[iter->second.temperature-16].second+=1;
        if(iter->second.wind>0){
            (instance->windSta[iter->second.wind-1].second)+=1;
            iter->second.windSta[iter->second.wind-1].second+=1;
        }
        if(iter->second.isServed==true){
            if((((mode==false)&&(iter->second.currentTemperature>iter->second.temperature))||
                ((mode==true)&&(iter->second.temperature<iter->second.currentTemperature)))){
                charge(_roomNo);
            }
            //charge(_roomNo);
            double diff(0);
            if(iter->second.wind>0){
                switch(iter->second.wind){
                case 1:
                    diff=0.05;
                    break;
                case 2:
                    diff=0.1;
                    break;
                case 3:
                    diff=0.2;
                    break;
                default:
                    break;
                }
            }

            if((((mode==false)&&(iter->second.currentTemperature-diff<iter->second.temperature)&&iter->second.wind>0)||
                ((mode==true) &&(iter->second.currentTemperature+diff>iter->second.temperature)&&iter->second.wind>0))){
                iter->second.currentTemperature=iter->second.temperature;
                (instance->achieveTempTime)++;
                (iter->second.achieveTempTime)++;
                iter->second.wind=0;
                QSqlQuery query(database::getInstance()->getDB());
                auto ret=query.exec(QString("INSERT INTO records VALUES(%1,%2,'%3',%4,%5,%6,%7,'%8',%9)")
                           .arg(QString().setNum(iter->second.sessionId))
                           .arg(QString().setNum(time(nullptr)))
                           .arg(QString::fromStdString(iter->second.roomNo))
                           .arg(QString().setNum(static_cast<int>(iter->second.temperature)))
                           .arg(QString().setNum(iter->second.wind))
                           .arg(QString().setNum(iter->second.isOpen?1:0))
                           .arg(QString().setNum(iter->second.isServed?1:0))
                           .arg(QString("sleep"))
                           .arg(QString().setNum(iter->second.cost)));
                //assert(ret);
                iter->second.isServed=false;
                iter->second.timeLeft=timeslot;
                //swapInAndOut
                if(!roomsInQueue.empty()){
                    auto minItor=std::min_element(roomsInQueue.begin(),roomsInQueue.end(),
                                                  [](roomInfo *i,roomInfo *j)->bool{return i->timeLeft<j->timeLeft;});
                    auto tmp=std::find(roomsBeServered.begin(),roomsBeServered.end(),&(iter->second));
                    if(minItor!=roomsInQueue.end()){
                        (*minItor)->wind=(*minItor)->s_wind;
                        (*minItor)->isServed=true;
                        ((*minItor)->scheduleTime)++;
                    }
                    roomsInQueue.push_back((*tmp));
                    roomsBeServered.push_back((*minItor));
                    roomsInQueue.erase(minItor);
                    roomsBeServered.erase(tmp);
                }
                else{
                    auto tmp=std::find(roomsBeServered.begin(),roomsBeServered.end(),&(iter->second));
                    roomsInQueue.push_back((*tmp));
                    roomsBeServered.erase(tmp);
                }
            }
            else{
                cout<<"change\n";
                iter->second.currentTemperature=temperature;
            }

            /*if(fabs(iter->second.currentTemperature-iter->second.temperature)<0.1&&iter->second.wind>0){
                (instance->achieveTempTime)++;
                (iter->second.achieveTempTime)++;
                iter->second.wind=0;
                QSqlQuery query(database::getInstance()->getDB());
                auto ret=query.exec(QString("INSERT INTO records VALUES(%1,%2,'%3',%4,%5,%6,%7,'%8',%9)")
                           .arg(QString().setNum(iter->second.sessionId))
                           .arg(QString().setNum(time(nullptr)))
                           .arg(QString::fromStdString(iter->second.roomNo))
                           .arg(QString().setNum(static_cast<int>(iter->second.temperature)))
                           .arg(QString().setNum(iter->second.wind))
                           .arg(QString().setNum(iter->second.isOpen?1:0))
                           .arg(QString().setNum(iter->second.isServed?1:0))
                           .arg(QString("sleep"))
                           .arg(QString().setNum(iter->second.cost)));
                assert(ret);
                iter->second.isServed=false;
                iter->second.timeLeft=timeslot;
                //swapInAndOut
                if(!roomsInQueue.empty()){
                    auto minItor=std::min_element(roomsInQueue.begin(),roomsInQueue.end(),
                                                  [](roomInfo *i,roomInfo *j)->bool{return i->timeLeft<j->timeLeft;});
                    auto tmp=std::find(roomsBeServered.begin(),roomsBeServered.end(),&(iter->second));
                    if(minItor!=roomsInQueue.end()){
                        (*minItor)->wind=(*minItor)->s_wind;
                        (*minItor)->isServed=true;
                        ((*minItor)->scheduleTime)++;
                    }
                    roomsInQueue.push_back((*tmp));
                    roomsBeServered.push_back((*minItor));
                    roomsInQueue.erase(minItor);
                    roomsBeServered.erase(tmp);
                }
                else{
                    auto tmp=std::find(roomsBeServered.begin(),roomsBeServered.end(),&(iter->second));
                    roomsInQueue.push_back((*tmp));
                    roomsBeServered.erase(tmp);
                }
            }*/
            roomLock.unlock();
            return;
        }
        else{
            iter->second.currentTemperature=temperature;
            if(iter->second.timeLeft>0)
                iter->second.timeLeft-=1;
            if(iter->second.timeLeft==0&&
                    (((mode==false)&&(iter->second.currentTemperature-iter->second.temperature>tempDiff))||
                    ((mode==true)&&(iter->second.temperature-iter->second.currentTemperature>tempDiff)))){
                if(roomsBeServered.size()>=maxRoomInService){
                    auto maxItor=std::max_element(roomsBeServered.begin(),roomsBeServered.end(),
                                                  [](roomInfo *i,roomInfo *j)->bool{return i->runtime<j->runtime;});
                    auto tmp=std::find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second));
                    (*maxItor)->isServed=false;
                    (*maxItor)->wind=0;
                    (*maxItor)->timeLeft=timeslot;
                    (*tmp)->isServed=true;
                    ((*tmp)->scheduleTime)++;
                    (*tmp)->wind=(*tmp)->s_wind;
                    roomsInQueue.push_back((*maxItor));
                    roomsBeServered.push_back((*tmp));
                    roomsInQueue.erase(tmp);
                    roomsBeServered.erase(maxItor);
                }
                else{
                    auto tmp=std::find(roomsInQueue.begin(),roomsInQueue.end(),&(iter->second));
                    (*tmp)->isServed=true;
                    (*tmp)->wind=(*tmp)->s_wind;
                    roomsBeServered.push_back((*tmp));
                    roomsInQueue.erase(tmp);
                }
            }
            roomLock.unlock();
            return;
        }


        /*else if((iter->second.isOpen==true)&&(iter->second.isServed==true)&&(iter->second.wind>0)){
            cout<<"enter 1"<<endl;
            charge(_roomNo);
            if(fabs(iter->second.currentTemperature-iter->second.temperature)<iter->second.wind*0.1){
                iter->second.wind=0;
                QSqlQuery query(database::getInstance()->getDB());
                auto ret=query.exec(QString("INSERT INTO records VALUES(%1,%2,'%3',%4,%5,%6,%7,%8,'%9')")
                           .arg(QString().setNum(iter->second.sessionId))
                           .arg(QString().setNum(time(nullptr)))
                           .arg(QString::fromStdString(iter->second.roomNo))
                           .arg(QString().setNum(static_cast<int>(iter->second.temperature)))
                           .arg(QString().setNum(iter->second.wind))
                           .arg(QString().setNum(iter->second.isOpen?1:0))
                           .arg(QString().setNum(iter->second.isServed?1:0))
                           .arg(QString().setNum(iter->second.cost))
                           .arg(QString("Sleep")));
                //assert(ret);

                //swapInAndOut

            }
            roomLock.unlock();
            cout<<"c unlock"<<endl;
            return;
        }
        else if(iter->second.isOpen==true&&iter->second.isServed==true&&iter->second.wind==0){
            if(((mode==false)&&(iter->second.currentTemperature-iter->second.temperature>tempDiff))||
                    ((mode==true)&&(iter->second.temperature-iter->second.currentTemperature>tempDiff))){
                iter->second.wind=iter->second.s_wind;
            }
            roomLock.unlock();
            cout<<"c unlock"<<endl;
            return;
        }
        else{
            roomLock.unlock();
            cout<<"c unlock"<<endl;
            return;
        }*/
    }
};

#endif // ROOM_HPP_INCLUDED
